package lumis.portal.serviceinstance;

import lumis.portal.AccessDeniedException;
import lumis.portal.PortalException;
import lumis.portal.PortalObjectNotFoundException;
import lumis.portal.UnexpectedException;
import lumis.portal.authentication.SessionConfig;
import lumis.portal.cache.PortalCache;
import lumis.portal.channel.ChannelConfig;
import lumis.portal.channel.acl.ChannelPermissions;
import lumis.portal.dao.DaoFactory;
import lumis.portal.event.persistence.PostAddEvent;
import lumis.portal.event.persistence.PostDeleteEvent;
import lumis.portal.event.persistence.PostUpdateEvent;
import lumis.portal.event.persistence.PreDeleteEvent;
import lumis.portal.localization.ILocalizationManager;
import lumis.portal.manager.ManagerException;
import lumis.portal.manager.ManagerFactory;
import lumis.portal.manager.ManagerFactoryInternal;
import lumis.portal.serialization.DeserializationConfig;
import lumis.portal.serialization.DeserializationContext;
import lumis.portal.service.IService;
import lumis.portal.service.IServiceManager;
import lumis.portal.service.ServiceConfig;
import lumis.portal.service.acl.ServicePermissions;
import lumis.portal.serviceinterfaceinstance.IServiceInterfaceInstanceManager;
import lumis.portal.serviceinterfaceinstance.ServiceInterfaceInstanceConfig;
import lumis.portal.transaction.PortalTransactionFactory;
import lumis.util.IResource;
import lumis.util.ITransaction;
import lumis.util.PortalUtil;
import lumis.util.TextUtil;
import lumis.util.XmlUtil;
import lumis.util.security.acl.AccessControlList;

import java.io.OutputStream;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.w3c.dom.Node;

/**
 * Service instance manager implementation.
 * 
 * @since 4.0.0
 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
 */
public class ServiceInstanceManager implements IServiceInstanceManager
{
	/**
	 * Enum of audit types used by service instance.
	 *
	 * @version $Revision: 13093 $ $Date: 2011-05-28 18:40:18 -0300 (Sat, 28 May 2011) $
	 * @since 6.0.0
	 */
	private static enum AuditType
	{
		ADD("lumis.portal.serviceinstance.ServiceInstanceConfig#add", "Added service instance"),
		UPDATE("lumis.portal.serviceinstance.ServiceInstanceConfig#update", "Updated service instance"),
		DELETE("lumis.portal.serviceinstance.ServiceInstanceConfig#delete", "Deleted service instance");
		
		String key;
		String title;
		
		AuditType(String key, String title)
		{
			this.key = key;
			this.title = title;
		}
	}

	private PortalCache<ServiceInstanceConfig> serviceInstanceConfigs = new PortalCache<ServiceInstanceConfig>("lumis.portal.serviceinstance.ServiceInstanceConfig");
	private PortalCache<Map<String, String>> serviceInstancePropertiesTable = new PortalCache<Map<String, String>>("lumis.portal.serviceinstance.ServiceInstanceConfig.customProperties");
	
	/**
	 * Cache used by {@link #getIdsByChannelId(SessionConfig, String, ITransaction)}.
	 * @since 4.2.0
	 */
	private PortalCache<Collection<String>> serviceInstanceIdsByChannelIdCache = new PortalCache<Collection<String>>("lumis.portal.channel.ChannelConfig.serviceInstances");

	public void addOrUpdate(SessionConfig sessionConfig, ServiceInstanceConfig serviceInstanceConfig, ITransaction transaction) throws ManagerException, PortalException
	{
		addOrUpdate(sessionConfig, serviceInstanceConfig, false, transaction);
	}
	
	public synchronized ServiceInstanceConfig get(String serviceInstanceId, ITransaction transaction) throws PortalException
	{
		ServiceInstanceConfig serviceInstanceConfig = serviceInstanceConfigs.get(serviceInstanceId);
		if (serviceInstanceConfig == null)
		{
			serviceInstanceConfig = DaoFactory.getServiceInstanceDao().get(serviceInstanceId, transaction);
			serviceInstanceConfigs.put(serviceInstanceId, serviceInstanceConfig);
		}
		return serviceInstanceConfig;
	}
	
	public synchronized ServiceInstanceDependencyConfig getDependency(String serviceInstanceDependencyId, ITransaction transaction) throws PortalException
	{
		return DaoFactory.getServiceInstanceDao().getServiceInstanceDependency(serviceInstanceDependencyId, transaction);
	}

	public ServiceInstanceConfig findServiceInstanceThroughChannelTree(SessionConfig sessionConfig, String channelId, String serviceId, ITransaction transaction) throws PortalException
	{
		Collection<String> ids = DaoFactory.getServiceInstanceDao().getIdsByChannelIdAndServiceId(channelId, serviceId, transaction);
		switch (ids.size())
		{
			case 0: 
				ChannelConfig channel = ManagerFactory.getChannelManager().get(sessionConfig, channelId, transaction);
				String channelParentId = channel.getParentId();
				if (channelParentId != null)
					return findServiceInstanceThroughChannelTree(sessionConfig, channelParentId, serviceId, transaction);
				else
					return null;
			case 1:
				return get(ids.iterator().next(), transaction);
			default:
				return null;
		}
	}

	public String add(SessionConfig sessionConfig, ServiceInstanceConfig serviceInstanceConfig, ITransaction transaction) throws PortalException
	{
		return add(sessionConfig, serviceInstanceConfig, false, transaction);
	}

	public void update(SessionConfig sessionConfig, ServiceInstanceConfig serviceInstanceConfig, ITransaction transaction) throws PortalException
	{			
		if(!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, serviceInstanceConfig.getChannelId(), ChannelPermissions.MANAGE_SERVICE_INSTANCE, transaction))
			throw new AccessDeniedException();
		
		if (mustUpdateFriendlyId(sessionConfig, serviceInstanceConfig, transaction))
		{
			serviceInstanceConfig.setAutoGeneratedFriendlyId();
		}

		if (serviceInstanceConfig.getFriendlyId() == null)
			throw new ManagerException("STR_FRIENDLY_ID_CANNOT_BE_NULL");

		if (!serviceInstanceConfig.mayUseFriendlyId(serviceInstanceConfig.getFriendlyId()))
			throw new ManagerException("STR_FRIENDLY_ID_CANNOT_BE_USED;"
					+ serviceInstanceConfig.getFriendlyId());

		ServiceInstanceConfig oldConfig = get(serviceInstanceConfig.getId(), transaction);
		
		// check if service instance is beeing moved
		if(!oldConfig.getChannelId().equals(serviceInstanceConfig.getChannelId()))
		{
			// if moving, must have manage_service_instance_security on both channels.
			if(!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, serviceInstanceConfig.getChannelId(), ChannelPermissions.MANAGE_SERVICE_INSTANCE_SECURITY, transaction)
					|| !ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, oldConfig.getChannelId(), ChannelPermissions.MANAGE_SERVICE_INSTANCE_SECURITY, transaction))
				throw new AccessDeniedException();
		}
		
		// update service instance's data
		DaoFactory.getServiceInstanceDao().update(serviceInstanceConfig, transaction);
		serviceInstanceConfigs.remove(serviceInstanceConfig.getId(), transaction);
		serviceInstanceIdsByChannelIdCache.clear(transaction);
		
		// get service instance's channel acl
		ChannelConfig channelConfig = ManagerFactory.getChannelManager().get(sessionConfig, serviceInstanceConfig.getChannelId(), transaction);
		AccessControlList acl = ManagerFactory.getServiceInstanceAclManager().get(sessionConfig, serviceInstanceConfig.getId(), transaction);
		
		// if acl's parent is not channel's acl (service instance's channel has changed)
		if (!channelConfig.getAccessControlListId().equals(acl.getParentId()))
		{
			// change the acl's parent to channel's acl
			acl.setParentId(channelConfig.getAccessControlListId());
			ManagerFactory.getServiceInstanceAclManager().update(sessionConfig, serviceInstanceConfig.getId(), acl, transaction);
		}

		// send update event
		PostUpdateEvent event = new PostUpdateEvent(sessionConfig, ServiceInstanceConfig.class,
				oldConfig, serviceInstanceConfig, transaction);
		ManagerFactory.getPortalEventManager().notifyObservers(event);
		
		// send render data changed event
		ServiceInstanceRenderDataChangedEvent dataChangedEvent = new ServiceInstanceRenderDataChangedEvent(
				sessionConfig, Collections.singleton(serviceInstanceConfig), transaction); 
		ManagerFactory.getPortalEventManager().notifyObservers(dataChangedEvent);
		
		// audit
		audit(sessionConfig, AuditType.UPDATE, serviceInstanceConfig, transaction);
	}

	public void delete(SessionConfig sessionConfig, String serviceInstanceId, boolean force, ITransaction transaction) throws PortalException
	{
		ServiceInstanceConfig serviceInstanceConfig = get(serviceInstanceId, transaction);
		if(!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, serviceInstanceConfig.getChannelId(), ChannelPermissions.MANAGE_SERVICE_INSTANCE, transaction))
			throw new AccessDeniedException();
		
		// verify service instance's dependencies
		if(!force)
		{
			Collection<String> dependentServiceInstanceIds = DaoFactory.getServiceInstanceDao().getIdsOfDependentServiceInstances(serviceInstanceId, transaction);
			if (!dependentServiceInstanceIds.isEmpty())
			{
				ServiceInstanceConfig dependentServiceInstance = get(dependentServiceInstanceIds.iterator().next(), transaction);
				String channelPath = ManagerFactory.getChannelManager().getPath(sessionConfig, dependentServiceInstance.getChannelId(), transaction);
				throw new ManagerException("STR_SERVICE_INSTANCE_HAS_DEPENDENT_SERVICE_INSTANCE;" + 
						TextUtil.escapeLocalizationParameter(serviceInstanceConfig.getName()) + ";" +  
						TextUtil.escapeLocalizationParameter(channelPath + "/" + dependentServiceInstance.getName()));
			}
		}

		// verify if the service instance has any interface instantiated
		Collection<String> interfaceInstanceIds = ManagerFactory.getServiceInterfaceInstanceManager().getIdsByServiceInstanceId(sessionConfig, serviceInstanceId, transaction);
		for(String interfaceInstanceId: interfaceInstanceIds)
		{
			ServiceInterfaceInstanceConfig interfaceInstanceConfig = ManagerFactory.getServiceInterfaceInstanceManager().get(sessionConfig, interfaceInstanceId, transaction);
			if(!interfaceInstanceConfig.isCreatedRunTime())
			{
				String pageId = interfaceInstanceConfig.getPageId();
				String pagePath = pageId != null ? ManagerFactory.getPageManager().getPath(sessionConfig, pageId, transaction) : null;
				throw new ManagerException("STR_SERVICE_INSTANCE_HAS_DEPENDENT_INTERFACE_INSTANCE;" + 
						TextUtil.escapeLocalizationParameter(serviceInstanceConfig.getName()) + ";" + 
						interfaceInstanceConfig.getId() + ";" + 
						(pagePath != null ? TextUtil.escapeLocalizationParameter(pagePath) : ""));
			}
		}
		
		// must remove all custom properties from this service instance
		// before deleting it
		IServiceInstanceDao serviceInstanceDao = DaoFactory.getServiceInstanceDao();
		serviceInstanceDao.deleteCustomProperties(serviceInstanceId, transaction);
		serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);

		// delete all child interface instances
		IServiceInterfaceInstanceManager interfaceInstanceManager = ManagerFactory.getServiceInterfaceInstanceManager();
		Collection<String> interfaceInstancesId = interfaceInstanceManager.getIdsByServiceInstanceId(sessionConfig, serviceInstanceId, transaction);
		for (String curInterfaceInstanceId : interfaceInstancesId)
		{
			interfaceInstanceManager.delete(sessionConfig, curInterfaceInstanceId, transaction);
		}

		IService service = ManagerFactory.getServiceManager().getService(sessionConfig, serviceInstanceConfig.getServiceId(), transaction);

		// TODO Kishnan: instanceDeleted should be beforeDeleteInstance ...
		// and call before actually deleting from the db.
		service.instanceDeleted(sessionConfig, serviceInstanceConfig, transaction);

		PreDeleteEvent preEvent = new PreDeleteEvent(sessionConfig, ServiceInstanceConfig.class, serviceInstanceConfig, transaction);
		ManagerFactory.getPortalEventManager().notifyObservers(preEvent);
		
		// finally delete service instance
		serviceInstanceDao.delete(serviceInstanceId, transaction);
		serviceInstanceConfigs.remove(serviceInstanceId, transaction);
		serviceInstanceIdsByChannelIdCache.clear(transaction);
		
		// delete service instance's acl
		ManagerFactory.getServiceInstanceAclManager().delete(serviceInstanceConfig.getAccessControlListId(), transaction);

		PostDeleteEvent postEvent = new PostDeleteEvent(sessionConfig, ServiceInstanceConfig.class, serviceInstanceConfig, transaction);
		ManagerFactory.getPortalEventManager().notifyObservers(postEvent);
		
		// audit
		audit(sessionConfig, AuditType.DELETE, serviceInstanceConfig, transaction);		
	}

	public Collection<String> getIds(SessionConfig sessionConfig, ITransaction transaction) throws PortalException
	{
		// TODO: validate userSessionsId

		return DaoFactory.getServiceInstanceDao().getIdsByChannelIdAndServiceId(null, null, transaction);
	}

	public Collection<String> getIdsByChannelId(SessionConfig sessionConfig, 
			String channelId, ITransaction transaction) throws PortalException
	{
		// TODO: validate userSessionsId

		// make sure channelId exists. Since channel removal does not trigger the
		// cache eviction, this must be done before using the serviceInstanceIdsByChannelIdCache
		ManagerFactory.getChannelManager().get(sessionConfig, channelId, transaction);
		
		// get the service instance identifiers
		Collection<String> result = serviceInstanceIdsByChannelIdCache.get(channelId);
		if (result == null)
		{
			result = DaoFactory.getServiceInstanceDao().getIdsByChannelIdAndServiceId(
					channelId, null, transaction);
			serviceInstanceIdsByChannelIdCache.put(channelId, result);
		}
		
		return result;
	}

	public Collection<String> getIdsByChannelTree(SessionConfig sessionConfig, String rootChannelId, ITransaction transaction) throws PortalException
	{
		// TODO: validate userSessionsId

		ArrayList<String> serviceInstanceIds = new ArrayList<String>();
		addIdsByChannelId(sessionConfig, rootChannelId, transaction, serviceInstanceIds);
		return serviceInstanceIds;
	}

	private void addIdsByChannelId(SessionConfig sessionConfig, String channelId, 
			ITransaction transaction, ArrayList<String> serviceInstanceIds) throws PortalException
	{
		serviceInstanceIds.addAll(getIdsByChannelId(sessionConfig, channelId, transaction));
		
		for (String subChannelId : ManagerFactory.getChannelManager().getIdsByParentChannelId(sessionConfig, channelId, transaction))
			addIdsByChannelId(sessionConfig, subChannelId, transaction, serviceInstanceIds);
	}

	public Collection<String> getIdsByServiceId(SessionConfig sessionConfig, String serviceId, ITransaction transaction) throws PortalException
	{
		// TODO: validate userSessionsId
		return DaoFactory.getServiceInstanceDao().getIdsByChannelIdAndServiceId(null, serviceId, transaction);
	}

	public String getCustomProperty(SessionConfig sessionConfig, String serviceInstanceId, String name, ITransaction transaction) throws PortalException
	{
		return getCustomProperties(sessionConfig, serviceInstanceId, transaction).get(name);
	}

	public void setCustomProperty(SessionConfig sessionConfig, String serviceInstanceId, String name, String value, ITransaction transaction) throws PortalException
	{
		DaoFactory.getServiceInstanceDao().setCustomProperty(serviceInstanceId, name, value, transaction);

		// clear from cache
		serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);
	}

	public void setCustomProperties(SessionConfig sessionConfig, String serviceInstanceId, Map<String, String> propertiesTable, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			IServiceInstanceDao serviceInstanceDao = DaoFactory.getServiceInstanceDao();

			for (String name : propertiesTable.keySet())
				serviceInstanceDao.setCustomProperty(serviceInstanceId, name, propertiesTable.get(name), transaction);

			// clear cache
			serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}

	public void deleteCustomProperty(SessionConfig sessionConfig, String serviceInstanceId, String name, ITransaction transaction) throws PortalException
	{
		DaoFactory.getServiceInstanceDao().deleteCustomProperty(serviceInstanceId, name, transaction);
		
		// clear from cache
		serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);
	}

	public void deleteCustomProperties(SessionConfig sessionConfig, String serviceInstanceId, ITransaction transaction) throws PortalException
	{
		DaoFactory.getServiceInstanceDao().deleteCustomProperties(serviceInstanceId, transaction);

		serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);
	}

	public void serialize(SessionConfig sessionConfig, String serviceInstanceId, OutputStream outputStream, ITransaction transaction) throws PortalException
	{
		// TODO: validate userSessionId
		
		ServiceInstanceConfig serviceInstanceConfig = get(serviceInstanceId, transaction);
		serviceInstanceConfig.serialize(sessionConfig, outputStream, transaction);
	}
	
	public String deserialize(SessionConfig sessionConfig, DeserializationConfig deserializationConfig, Node serviceInstanceNode, ITransaction transaction) throws PortalException
	{
		ServiceInstanceConfig serviceInstanceConfig = new ServiceInstanceConfig();
		serviceInstanceConfig.deserialize(serviceInstanceNode);

		if (deserializationConfig.getCreateNewIds())
		{
			String newId = PortalUtil.generateNewGuid();
			deserializationConfig.setIdMapping(serviceInstanceConfig.getId(), newId);
			serviceInstanceConfig.setId(newId);
		}

		// now fix ids based on mappings
		if (serviceInstanceConfig.getChannelId() != null && deserializationConfig.containsIdMapping(serviceInstanceConfig.getChannelId()))
			serviceInstanceConfig.setChannelId(deserializationConfig.getIdMapping(serviceInstanceConfig.getChannelId()));
		
		if(serviceInstanceConfig.getAccessControlListId() != null && deserializationConfig.containsIdMapping(serviceInstanceConfig.getAccessControlListId()))
			serviceInstanceConfig.setAccessControlListId(deserializationConfig.getIdMapping(serviceInstanceConfig.getAccessControlListId()));

		if(deserializationConfig.isUpdateIfElementExists())
			addOrUpdate(sessionConfig, serviceInstanceConfig, true, transaction);
		else
			add(sessionConfig, serviceInstanceConfig, true, transaction);
		
		IService service = ManagerFactory.getServiceManager().getService(sessionConfig, serviceInstanceConfig.getServiceId(), transaction);
		service.instanceDeserialized(sessionConfig, serviceInstanceConfig.getId(), deserializationConfig, serviceInstanceNode, transaction);			

		return serviceInstanceConfig.getId();
	}

	public ServiceInstanceConfig getServiceInstanceByDependency(SessionConfig sessionConfig, String serviceInstanceId, String dependencyType, ITransaction transaction) throws ManagerException, PortalException
	{
		Collection<String> ids = DaoFactory.getServiceInstanceDao().getIdsByServiceInstanceAndDependencyType(serviceInstanceId, dependencyType, transaction);
		if (!ids.isEmpty())
		{
			ServiceInstanceConfig serviceInstanceConfig = get(ids.iterator().next(), transaction);
			return serviceInstanceConfig;
		}
		else
			return null;
	}
	
	public void setServiceInstanceDependency(ServiceInstanceDependencyConfig serviceInstanceDependencyConfig, ITransaction transaction) throws PortalException
	{
		DaoFactory.getServiceInstanceDao().removeServiceInstanceDependencies(serviceInstanceDependencyConfig.getDependentServiceInstanceId(), serviceInstanceDependencyConfig.getDependencyType(), transaction);
		if (serviceInstanceDependencyConfig.getServiceInstanceId() != null)
			DaoFactory.getServiceInstanceDao().addServiceInstanceDependency(serviceInstanceDependencyConfig, transaction);
	}

	public void serializeDependencies(SessionConfig sessionConfig, String serviceInstanceId, OutputStream outputStream, ITransaction transaction) throws PortalException
	{
		Collection<String> ids = getDependencyIdsByServiceInstanceId(sessionConfig, serviceInstanceId, transaction);
		ServiceInstanceDependencyConfig dependencyConfig;
		if(ids != null)
		{
			for(String id : ids)
			{
				dependencyConfig = DaoFactory.getServiceInstanceDao().getServiceInstanceDependency(id, transaction);
				dependencyConfig.serialize(dependencyConfig, outputStream);
			}
		}
		
	}

	public Collection<String> getDependencyIdsByServiceInstanceId(SessionConfig sessionConfig, String serviceInstanceId, ITransaction transaction) throws ManagerException, PortalException
	{
		Collection<String> ids = DaoFactory.getServiceInstanceDao().getDependencyIdsByServiceInstanceId(serviceInstanceId, transaction);
		if (!ids.isEmpty())
			return ids;
		else
			return new ArrayList<String>();
	}
	
	public Collection<String> getDependentServiceInstanceIds(SessionConfig sessionConfig, String serviceInstanceId, ITransaction transaction) throws PortalException
	{
		return DaoFactory.getServiceInstanceDao().getIdsOfDependentServiceInstances(serviceInstanceId, transaction);
	}

	public Collection<ServiceInstanceConfig> getDependentServiceInstances(SessionConfig sessionConfig, String serviceInstanceId, ITransaction transaction) throws PortalException
	{
		Collection<String> dependentServiceInstanceIds = DaoFactory.getServiceInstanceDao().getIdsOfDependentServiceInstances(serviceInstanceId, transaction);
		Collection<ServiceInstanceConfig> serviceInstances = new ArrayList<ServiceInstanceConfig>(dependentServiceInstanceIds.size());
		for (String id: dependentServiceInstanceIds)
		{
			ServiceInstanceConfig config = get(id, transaction);
			serviceInstances.add(config);
		}
		return serviceInstances;
	}
	
	public String deserializeDependency(SessionConfig sessionConfig, DeserializationConfig deserializationConfig, Node serviceInstanceDependencyNode, ITransaction transaction) throws PortalException
	{
		ServiceInstanceDependencyConfig serviceInstanceDependencyConfig = new ServiceInstanceDependencyConfig();
		serviceInstanceDependencyConfig.deserialize(serviceInstanceDependencyNode);
		
		String mappedServiceInstanceId;
		String mappedDependentServiceInstanceId;
		
		if (deserializationConfig.getCreateNewIds())
		{
			mappedServiceInstanceId = deserializationConfig.getIdMapping(serviceInstanceDependencyConfig.getServiceInstanceId());
			mappedDependentServiceInstanceId = deserializationConfig.getIdMapping(serviceInstanceDependencyConfig.getDependentServiceInstanceId());
			
			// check if the service in which this one depends was imported on this file. if not, the dependency is not imported
			// TODO: This variable never receive null. Thus, the logic is wrong.
			if(mappedServiceInstanceId == null)
				return null;
			
			String newId = PortalUtil.generateNewGuid();
			deserializationConfig.setIdMapping(serviceInstanceDependencyConfig.getId(), newId);
			serviceInstanceDependencyConfig.setId(newId);

			serviceInstanceDependencyConfig.setServiceInstanceId(mappedServiceInstanceId);
			serviceInstanceDependencyConfig.setDependentServiceInstanceId(mappedDependentServiceInstanceId);
		}
		else
		{
			// makes sure the service instance exists
			ServiceInstanceConfig serviceInstance = get(serviceInstanceDependencyConfig.getServiceInstanceId(), transaction);
			assert serviceInstance != null;
		}

		setServiceInstanceDependency(serviceInstanceDependencyConfig, transaction);
		
		return serviceInstanceDependencyConfig.getId();
	}
	
	
	public Map<String, String> getCustomProperties(SessionConfig sessionConfig, String serviceInstanceId, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			Map<String, String> propertyValuesTable = serviceInstancePropertiesTable.get(serviceInstanceId); 
			if (propertyValuesTable == null)
			{
				propertyValuesTable = Collections.unmodifiableMap(DaoFactory.getServiceInstanceDao().getCustomProperties(serviceInstanceId, transaction));
				serviceInstancePropertiesTable.put(serviceInstanceId, propertyValuesTable);
			}
			return new HashMap<String, String>(propertyValuesTable);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	public void serializeCustomProperties(SessionConfig sessionConfig, String serviceInstanceId, OutputStream outputStream, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			ServiceInstanceConfig serviceInstanceConfig = get(serviceInstanceId, transaction);

			Map<String, String> customProperties = getCustomProperties(sessionConfig, serviceInstanceId, transaction);

			if (customProperties != null && customProperties.size() > 0)
			{
				StringBuilder output = new StringBuilder();
				
				output.append("<sICustomProperties id=\"" + serviceInstanceId + "_customProperties\">");
				output.append("<serviceInstanceId>");
				output.append(serviceInstanceId);
				output.append("</serviceInstanceId>");
				
				output.append("<channelId>");
				output.append(serviceInstanceConfig.getChannelId());
				output.append("</channelId>");
				
				for (String propertyName : customProperties.keySet())
				{
					String propertyValue = customProperties.get(propertyName);
					if (propertyValue != null && propertyValue.length() > 0)
					{
						propertyValue = URLEncoder.encode(propertyValue, "UTF-8");
						output.append("<property name=\"");
						output.append(propertyName);
						output.append("\">");
						output.append(propertyValue);
						output.append("</property>");
					}
				}
				output.append("</sICustomProperties>");
				outputStream.write( output.toString().getBytes("UTF-8"));
			}
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	public void deserializeCustomProperties(SessionConfig sessionConfig, DeserializationConfig deserializationConfig, Node serviceInstanceCustomPropertiesNode, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			String serviceInstanceId = XmlUtil.readNodeString("serviceInstanceId", serviceInstanceCustomPropertiesNode);

			if (deserializationConfig.containsIdMapping(serviceInstanceId))
				serviceInstanceId = deserializationConfig.getIdMapping(serviceInstanceId);

			Node[] propertyNodes = XmlUtil.selectNodes("property", serviceInstanceCustomPropertiesNode);

			for (Node property : propertyNodes)
			{
				String name = XmlUtil.readAttributeString("name", property);
				String value = XmlUtil.getTextContent(property);
				value = URLDecoder.decode(value, "UTF-8");
				setCustomProperty(sessionConfig, serviceInstanceId, name, value, transaction);
			}
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	public void addOrUpdate(SessionConfig sessionConfig, ServiceInstanceConfig serviceInstanceConfig, boolean fromDeserialization, ITransaction transaction) throws ManagerException, PortalException
	{
		try
		{
			ServiceInstanceConfig serviceInstanceConfigTemp = null;
			
			try
			{
				serviceInstanceConfigTemp = get(serviceInstanceConfig.getId(), transaction);
			}
			catch (Exception e)
			{
				serviceInstanceConfigTemp = null;
			}
			
			if(serviceInstanceConfigTemp != null)
				update(sessionConfig, serviceInstanceConfig, transaction);
			else
				add(sessionConfig, serviceInstanceConfig, fromDeserialization, transaction);
		}
		catch (PortalException e)
		{
			throw e;
		}
		catch (Exception e)
		{
			throw new UnexpectedException(e);
		}
	}
	
	public String add(SessionConfig sessionConfig, ServiceInstanceConfig serviceInstanceConfig, boolean fromDeserialization, ITransaction transaction) throws PortalException
	{
		if(!ManagerFactory.getChannelAclManager().checkPermission(sessionConfig, serviceInstanceConfig.getChannelId(), ChannelPermissions.MANAGE_SERVICE_INSTANCE, transaction)
			|| !ManagerFactory.getServiceAclManager().checkPermission(sessionConfig, serviceInstanceConfig.getServiceId(), ServicePermissions.CREATE_SERVICE_INSTANCE, transaction))
			throw new AccessDeniedException();
		
		IServiceManager serviceManager = ManagerFactory.getServiceManager();

		if (serviceInstanceConfig.getId() == null)
			serviceInstanceConfig.setId(PortalUtil.generateNewGuid());

		// validate serviceId and fill in instance id, name and description
		// if null
		ServiceConfig serviceConfig = serviceManager.get(sessionConfig, serviceInstanceConfig.getServiceId(), transaction);
		if (serviceInstanceConfig.getName() == null || serviceInstanceConfig.getDescription() == null)
		{
			ILocalizationManager localizationManager = ManagerFactory.getLocalizationManager();
			IResource resource = serviceConfig.getResource();

			if (serviceInstanceConfig.getName() == null)
			{
				// get serviceinstance names already used in the channel
				Set<String> namesUsed = new HashSet<String>();
				Collection<String> ids = getIdsByChannelId(sessionConfig, serviceInstanceConfig.getChannelId(), transaction);
				for (String id : ids)
				{
					ServiceInstanceConfig siblingServiceInstanceConfig = get(id, transaction);
					namesUsed.add(siblingServiceInstanceConfig.getName());
				}

				// generate a newName (unique in the channel) for the
				// serviceInstance being added
				String defaultName = localizationManager.localize(serviceConfig.getName(), resource);
				String newName = defaultName;
				int count = 0;
				while (namesUsed.contains(newName))
				{
					count++;
					newName = defaultName + " (" + count + ")";
				}
				serviceInstanceConfig.setName(newName);
			}

			if (serviceInstanceConfig.getDescription() == null)
				serviceInstanceConfig.setDescription(localizationManager.localize(serviceConfig.getDescription(), resource));
		}

		if (serviceInstanceConfig.isAutomaticFriendlyId())
		{
			serviceInstanceConfig.setAutoGeneratedFriendlyId();
		}

		if (serviceInstanceConfig.getFriendlyId() == null)
			throw new ManagerException("STR_FRIENDLY_ID_CANNOT_BE_NULL");

		if (!serviceInstanceConfig.mayUseFriendlyId(serviceInstanceConfig.getFriendlyId()))
			throw new ManagerException("STR_FRIENDLY_ID_CANNOT_BE_USED;"
					+ serviceInstanceConfig.getFriendlyId());

		// perform database insertions
		if(serviceInstanceConfig.getAccessControlListId() == null)
			ManagerFactory.getServiceInstanceAclManager().add(sessionConfig, serviceInstanceConfig, transaction);
		DaoFactory.getServiceInstanceDao().add(serviceInstanceConfig, transaction);
		
		// evict channel.serviceInstances channel for parent channel 
		serviceInstanceIdsByChannelIdCache.remove(serviceInstanceConfig.getChannelId(), transaction);

		if(!fromDeserialization)
		{
			IService service = serviceManager.getService(sessionConfig, serviceInstanceConfig.getServiceId(), transaction);
			service.instanceAdded(sessionConfig, serviceInstanceConfig, transaction);
			service.autoAddDependencies(sessionConfig, serviceInstanceConfig, transaction);
		}
		
		// Notify observers
		PostAddEvent event = new PostAddEvent(sessionConfig, ServiceInstanceConfig.class, 
				serviceInstanceConfig, transaction);
		ManagerFactory.getPortalEventManager().notifyObservers(event);

		// audit		
		audit(sessionConfig, AuditType.ADD, serviceInstanceConfig, transaction);
		
		return serviceInstanceConfig.getId();
	}

	/**
	 * Call the audit API registering the service instance operations.
	 * @param sessionConfig		instance of SessionConfig.
	 * @param auditType			the audit operation type.
	 * @param sic				instance of ServiceInstanceConfig.
	 * @param transaction		the current transaction.
	 * @throws PortalException	if an error occur registering the audit operation.
	 * @since 6.0.0
	 */
	private void audit(SessionConfig sessionConfig,
						AuditType auditType,
						ServiceInstanceConfig sic,
						ITransaction transaction)
					throws PortalException
	{
		ChannelConfig channelConfig = null;
		if (sic.getChannelId() != null)
		{
			try
			{
				channelConfig = ManagerFactory.getChannelManager().get(sessionConfig, sic.getChannelId(), transaction);
			}
			catch (Exception e)
			{
			}
		}
		
		String title = auditType.title + ": " + sic.getName();
		String description = auditType.title + ":\r\n" 
								+ "  id: " + sic.getId() + "\r\n"
								+ "  name: " + sic.getName() + "\r\n"
								+ "  description: " + sic.getDescription() + "\r\n"
								+ "  service id: " + sic.getServiceId() + "\r\n"
								+ "  channel id: " + sic.getChannelId()
								+ ((channelConfig != null)?"\r\n  channel name: " + channelConfig.getName():"");
		
		ManagerFactoryInternal.getAuditManager().audit(title, description, auditType.key);
	}
	
	public void clearServiceInstanceCache(SessionConfig sessionConfig, String serviceInstanceId, ITransaction transaction) throws ManagerException, PortalException
	{
		ServiceInstanceConfig serviceInstanceConfig = get(serviceInstanceId, transaction);
		ManagerFactory.getServiceInstanceAclManager().removeFromCache(serviceInstanceConfig.getAccessControlListId(), transaction);
		serviceInstanceConfigs.remove(serviceInstanceId, transaction);
		serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);
		serviceInstanceIdsByChannelIdCache.clear(transaction);
	}

	/**
	 * Clears the service instance information cached in this manager.
	 * Currently this is an internal method used by {@link lumis.portal.channel.ChannelManager}. 
	 * @param serviceInstanceId the service instance identifier.
	 * @param transaction the transaction for persistence access.
	 * @since 4.1.0
	 */
	public void clearServiceInstanceMemoryCache(String serviceInstanceId, ITransaction transaction) throws PortalException
	{
		serviceInstanceConfigs.remove(serviceInstanceId, transaction);
		serviceInstancePropertiesTable.remove(serviceInstanceId, transaction);
		serviceInstanceIdsByChannelIdCache.clear(transaction);
	}

	public void deleteConfig(DeserializationContext deserializationContext, String configId) throws PortalException
	{
		delete(deserializationContext.getSessionConfig(), configId, false, deserializationContext.getTransaction());
	}

	public boolean keyExists(SessionConfig sessionConfig, Object configKey, Object config, ITransaction transaction)
	{
		String serviceInstanceId = (String) configKey;
		if (serviceInstanceId == null)
			return true;

		ServiceInstanceConfig serviceInstanceConfig = null;
		try
		{
			serviceInstanceConfig = ManagerFactory.getServiceInstanceManager().get(serviceInstanceId, transaction);
		}
		catch (PortalObjectNotFoundException e)
		{
		}
		catch (PortalException e)
		{
			throw new IllegalStateException("Could not check a service instance key persistence.");
		}

		return serviceInstanceConfig != null;
	}

	/**
	 * Indicates if the given service instance must have its friendly identifier
	 * regenerated. This method must be called <em>before</em> the update is
	 * realized.
	 * 
	 * @param sessionConfig
	 *            the session config.
	 * @param serviceInstanceConfig
	 *            the service instance to be tested.
	 * @param transaction
	 *            the transaction.
	 * @return true if the given service instance must have its friendly
	 *         identifier regenerated, false otherwise.
	 * @since 6.0.0
	 */
	private boolean mustUpdateFriendlyId(SessionConfig sessionConfig,
			ServiceInstanceConfig serviceInstanceConfig, ITransaction transaction)
			throws PortalException
	{
		if (serviceInstanceConfig.isAutomaticFriendlyId()
				&& serviceInstanceConfig.getFriendlyId() == null)
			return true;

		ServiceInstanceConfig current = get(serviceInstanceConfig.getId(), transaction);
		if (!serviceInstanceConfig.isAutomaticFriendlyId())
			return false;

		boolean becomeAutomatic = !(current.isAutomaticFriendlyId())
				&& serviceInstanceConfig.isAutomaticFriendlyId();
		boolean changedName = !(current.getName().equals(serviceInstanceConfig.getName()));
		boolean changedParent = !(current.getChannelId().equals(serviceInstanceConfig
				.getChannelId()));
		return (changedName || changedParent || becomeAutomatic);
	}

	/**
	 * Indicates if the given service instance may use the give friendly
	 * identifier.
	 * 
	 * @param source
	 *            the service instance.
	 * @param friendlyId
	 *            the friendly identifier.
	 * @return true if the given service instance may use the give friendly
	 *         identifier, false otherwise.
	 * @since 6.0.0
	 */
	boolean mayUseFriendlyId(ServiceInstanceConfig source, String friendlyId)
			throws PortalException
	{
		return ((ServiceInstanceDaoJdbc) DaoFactory.getServiceInstanceDao())
				.getNumberOfServiceInstanceUsingSameFriendlyId(source.getId(), source
						.getChannelId(), friendlyId, PortalTransactionFactory
						.getCurrentTransaction()) == 0;
	}

}